<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>贪吃蛇</title>
  <style>
    body {
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .btn {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      width: 230px;
      height: 90px;
      line-height: 90px;
      text-align: center;
      color: #fff;
      font-size: 25px;
      text-transform: uppercase;
      cursor: pointer;
      background: linear-gradient(90deg, #03a9f4, #f441a5, #ffeb3b, #03a9f4);
      background-size: 400%;
      border-radius: 60px;
    }

    .btn:hover {
      animation: animate 8s linear infinite;
    }

    @keyframes animate {
      0% {
        background-position: 0%;
      }

      100% {
        background-position: 400%;
      }
    }

    .btn::before {
      content: "";
      position: absolute;
      top: -5px;
      left: -5px;
      right: -5px;
      bottom: -5px;
      z-index: -1;
      background: linear-gradient(90deg, #03a9f4, #f441a5, #ffeb3b, #03a9f4);
      background-size: 400%;
      border-radius: 40px;
      opacity: 0;
      transition: 0.5s;
    }

    .btn:hover::before {
      filter: blur(20px);
      opacity: 1;
      animation: animate 8s linear infinite;
    }
  </style>
</head>

<body>
  <!-- <b href="#" class="btn">button</b> -->
  <canvas id="canvas" width="500" height="500"></canvas>
  <script>
    const canvas = document.getElementById("canvas");
    const ctx = canvas.getContext("2d");
    class Snake {
      constructor(options = {}) {
        const {
          mapSize, //地图大小
          ctx, //canvas
          blockSize = 10, //方块大小
        } = options;
        this.mapSize = mapSize;
        this.centerX = mapSize / 2;
        this.centerY = mapSize / 2;
        this.blockSize = blockSize;
        this.ctx = ctx;
        this.direction = "left";
        this.randomBlock = [this.getRandom() * 10, this.getRandom() * 10];
        this.snakeArr = Array.from({
          length: 5
        }, (e, i) => {
          return [this.centerX + this.blockSize * i, this.centerY];
        });
        this.timer = '';
      }
      //获得地图内随机数
      getRandom() {
        return Math.floor(Math.random() * (this.mapSize / this.blockSize));
      }
      //画随机方块
      createBlock() {
        this.ctx.fillStyle = "#f00";
        let [x, y] = this.randomBlock;
        this.ctx.fillRect(x, y, 10, 10);
      }
      //画蛇
      initSnake() {
        for (let v of this.snakeArr) {
          this.ctx.fillStyle = "#000";
          let [x, y] = v;
          this.ctx.fillRect(x, y, 10, 10);
        }
      }
      //画地图
      initMap() {
        this.ctx.fillStyle = "#cccccc";
        this.ctx.fillRect(0, 0, 500, 500);
      }
      render() {
        this.initMap();
        this.initSnake();
        this.createBlock();
        this.timer = setInterval(() => {
          this.initMap();
          this.initSnake();
          this.createBlock();
          this.testingCollision()
          this.moveSnake();
        }, 300);
      }
      //移动蛇
      moveSnake() {
        let last = JSON.parse(JSON.stringify(this.snakeArr));

        this.snakeArr = this.snakeArr.map((e, i) => {
          if (i === 0) {
            let block = [];
            let [x, y] = e;
            switch (this.direction) {
              case "left":
                block = [x - 10, y];
                break;
              case "right":
                block = [x + 10, y];
                break;
              case "top":
                block = [x, y - 10];
                break;
              case "bottom":
                block = [x, y + 10];
                break;
            }
            return block;
          } else {
            return this.snakeArr[i - 1];
          }
        });

        let [x, y] = this.snakeArr[0];
        let [blockX, blockY] = this.randomBlock;

        // 吃到食物
        if (x === blockX && y === blockY) {
          this.snakeArr.push(last[last.length - 1]);
          this.randomBlock = [this.getRandom() * 10, this.getRandom() * 10];
        }
      }
      // 检测碰撞
      testingCollision() {
        try {
          let [
            [head_x, head_y]
          ] = this.snakeArr;
          let futureHead = [];
          switch (this.direction) {
            case "left":
              futureHead = [head_x - 10, head_y];
              break;
            case "right":
              futureHead = [head_x + 10, head_y];
              break;
            case "top":
              futureHead = [head_x, head_y - 10];
              break;
            case "bottom":
              futureHead = [head_x, head_y + 10];
              break;
          }
          // 检测碰撞到身体
          let collisionBody = this.snakeArr.some(([body_x, body_y]) => body_x == futureHead[0] && body_y ==
            futureHead[
              1]);
          if (collisionBody)
            throw new Error('游戏结束');

          // 边界碰撞检测
          let collisionBorder = futureHead.some((val) => val < 0 || val > 500);
          if (collisionBorder)
            throw new Error('游戏结束')
        } catch (error) {
          let fraction = (this.snakeArr.length - 5) * 100
          clearInterval(this.timer);
          setTimeout(() => {
            alert(`${error} 得分：${fraction}`);
          })
        }
      };
    }
    let mySnake = new Snake({
      mapSize: 500,
      ctx,
    });
    let keyMap = new Map([
      [38, "top"],
      [39, "right"],
      [40, "bottom"],
      [37, "left"],
    ]);
    document.onkeydown = (e) => {
      let {
        keyCode
      } = e;
      let {
        direction
      } = mySnake;
      if (direction === "left" && keyCode === 39) return;
      if (direction === "right" && keyCode === 37) return;
      if (direction === "top" && keyCode === 40) return;
      if (direction === "bottom" && keyCode === 38) return;
      mySnake.direction = keyMap.get(keyCode);
    };

    mySnake.render();
  </script>
</body>

</html>